package commands;

import common.Strings;
import error.Errors;
import file.File;
import file.FileString;
import file.FileType;
import file.Files;

import java.util.ArrayList;

public abstract class Cmd
{
	String fileName;
	String fileAlias;
	int fileLine;

	Cmd parent;
	String originalText;
	int shift;
	String text;
	String comment;

	public ArrayList<Cmd> body;
	public static ArrayList<Cmd> emptyAndCommentStrings = new ArrayList<Cmd>();

	protected Cmd(FileString string)
	{
		this.fileName = string.getFileName();
		this.fileAlias = string.getFileAlias();
		this.fileLine = string.getLineNum();
		originalText = string.getValue();
		shift = Strings.getShift(string.getValue());
		ArrayList<String> strs = Strings.getTextAndComment(string.getValue());
		this.text = strs.get(0);
		this.comment = strs.get(1);
		body = new ArrayList<Cmd>();
	}

	public abstract CmdType getType();

	public void addCommandToBody(Cmd cmd)
	{
		cmd.parent = this;
		if (cmd.getType() == CmdType.EMPTY || cmd.getType() == CmdType.COMMENT)
		{
			if (emptyAndCommentStrings.size() == 0 || emptyAndCommentStrings.get(emptyAndCommentStrings.size() - 1)
					.getType() != CmdType.EMPTY)
				emptyAndCommentStrings.add(cmd);
			else if (cmd.getType() == CmdType.COMMENT)
				emptyAndCommentStrings.add(emptyAndCommentStrings.size() - 1, cmd);
		}
		else
		{
			if (body.size() == 0 || cmd.shift <= body.get(body.size() - 1).shift)
			{
				if (body.size() > 0 && cmd.shift < body.get(body.size() - 1).shift)
					Errors.addWarning(cmd.fileName, cmd.fileAlias, cmd.fileLine,
							"Сдвиг этой строки не соответствует сдвигу предыдущей строки");
				for (Cmd tCmd : emptyAndCommentStrings)
					body.add(tCmd);
				body.add(cmd);
				emptyAndCommentStrings.clear();
			}
			else
				body.get(body.size() - 1).addCommandToBody(cmd);
		}
	}

	int getLevel()
	{
		return parent.getLevel() + 1;
	}

	@Override
	public String toString()
	{
		String toOut = toOutput();
		if (toOut.length() > 0)
			toOut += " ";
		return getBeforeShift() + toOut + getComment() + " " + getAfterText();
	}

	String getBeforeShift()
	{
		int sh = getLevel();
		if (sh <= 0)
			return "";
		return String.format("%" + getLevel() + "s", "");
	}

	String getComment()
	{
		return ((comment.length() == 0) ? "" : "// " + comment);
	}

	String getAfterText()
	{
		return "(" + getType() + " Alias:" + fileAlias + ":" + fileLine + ")";
	}

	public abstract String toOutput();

	public void print2()
	{
		for (Cmd cmd : body)
		{
			System.out.println(cmd);
			if (cmd.body.size() > 0)
				cmd.print2();
		}
	}

	public void doIncludes()
	{
		for (int i = 0; i < body.size(); i++)
		{
			if (body.get(i).getType() == CmdType.EMPTY && body.get(i).body.size() > 0)
			{
				Errors.addError(fileName, fileAlias, fileLine, "Пустая строка не может иметь вложенное тело");
				return;
			}
			if (body.get(i).getType() == CmdType.COMMENT && body.get(i).body.size() > 0)
			{
				Errors.addError(fileName, fileAlias, fileLine,
						"Строка состоящая из комментария не может иметь вложенное тело");
				return;
			}
			if (body.get(i).getType() == CmdType.HZ_INCLUDE && body.get(i).body.size() > 0)
			{
				Errors.addError(fileName, fileAlias, fileLine, "@INCLUDE не может иметь вложенное тело");
				return;
			}
			if (body.get(i).getType() == CmdType.HZ_INCLUDE)
			{
				Errors.addError(fileName, fileAlias, fileLine,
						"ОШибка парсинга @INCLUDE инструкции (Формат комманды: '@include fileAlias [BEFORE|AFTER]')");
				return;
			}
			if (body.get(i).getType() == CmdType.INCLUDE_BEFORE && body.get(i).body.size() > 0)
			{
				Errors.addError(fileName, fileAlias, fileLine, "@INCLUDE не может иметь вложенное тело");
				return;
			}
			if (body.get(i).getType() == CmdType.INCLUDE_AFTER && body.get(i).body.size() > 0)
			{
				Errors.addError(fileName, fileAlias, fileLine, "@INCLUDE не может иметь вложенное тело");
				return;
			}

			if (body.get(i).getType() == CmdType.INCLUDE_BEFORE)
			{
				String incName = ((Include)(body.get(i))).fileName;
				String incAlias = ((Include)(body.get(i))).fileAliasToInclude;
				int incFileLine = ((Include)(body.get(i))).fileLine;
				File tFile = Files.baseFiles.getFile(incAlias);
				if (tFile == null)
				{
					Errors.addError(fileName, fileAlias, fileLine,
							"Алиас указывает на несуществующий файл или файл с ошибками");
					return;
				}
				if (tFile.fileType == FileType.STATIC || tFile.fileType == FileType.STATIC_INCLUDE)
				{
					Errors.addError(fileName, fileAlias, fileLine,
							"!!Файл с параметром PARSE=[STATIC|STATIC_ICLUDE] не может быть вставлен коммандой '@include BEFORE'");
					return;
				}
				ArrayList<Cmd> someBody = tFile.baseCommand.body;
				String incComment = body.get(i).getComment();
				body.remove(i);
				Cmd tCmd = Cmd.getCommand(new FileString(incName, incAlias, incFileLine, incComment));
				tCmd.parent = this;

				for (int t = 0; t < someBody.size(); t++)
				{
					someBody.get(t).parent = this;
					body.add(i + t, someBody.get(t));
				}
				body.add(i, tCmd);
			}
			else if (body.get(i).body.size() > 0)
			{
				if (Errors.getErrorCount() > 0)
					return;
				body.get(i).doIncludes();
			}
		}
	}

	public static Cmd getCommand(FileString string)
	{
		String text = Strings.getTextAndComment(string.getValue()).get(0);
		if (string.getValue().trim().startsWith("//"))
			return new Comment(string);
		if (text.trim().length() == 0)
			return new Empty(string);
		if (text.startsWith("@@"))
			return new Anchor(string);
		if (text.toLowerCase().startsWith("@include"))
			return new Include(string);
		if (text.startsWith("@"))
			return new Var(string);
		if (text.startsWith("&") || text.indexOf(':') < 0 || text.indexOf(':') >= 0 && doubleDotSameWithPseudoClass(
				text))
			return new Selector(string);
		if (text.indexOf(':') >= 0)
			return new Command(string);
		return new Hz(string);
	}

	static boolean doubleDotSameWithPseudoClass(String text)
	{
		String[] psevdoClasses = {
				":-moz-placeholder",
				":active",
				":after",
				":before",
				":checked",
				":default",
				":disabled",
				":empty",
				":enabled",
				":first-child",
				":first-letter",
				":first-line",
				":first-of-type",
				":focus",
				":hover",
				":indeterminate",
				":invalid",
				":lang",
				":last-child",
				":last-of-type",
				":link",
				":not",
				":nth-child",
				":nth-last-child",
				":nth-last-of-type",
				":nth-of-type",
				":only-child",
				":only-of-type",
				":optional",
				":read-only",
				":read-write",
				":required",
				":root",
				":target",
				":valid",
				":visited",
				"::-moz-selection",
				"::after",
				"::before",
				"::first-letter",
				"::first-line",
				"::selection"
		}; // String[] psevdoClasses
		int i = text.indexOf(':');
		for (String str : psevdoClasses)
			if (i == text.indexOf(str))
				return true;
		return false;
	}
}
